#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
//#include <zipfile/zipfile.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "sysdeps.h"

#define  TRACE_TAG  TRACE_ADB
#include "adb_client.h"

static transport_type __adb_transport = kTransportAny;
static const char* __adb_serial = NULL;

static int __adb_server_port = DEFAULT_ADB_PORT;

void adb_set_transport(transport_type type, const char* serial)
{
	__adb_transport = type;
	__adb_serial = serial;
}

void adb_set_tcp_specifics(int server_port)
{
	__adb_server_port = server_port;
}

int adb_get_emulator_console_port(void)
{
	const char*   serial = __adb_serial;
	int           port;

	if (serial == NULL) {
		/* if no specific device was specified, we need to look at */
		/* the list of connected devices, and extract an emulator  */
		/* name from it. two emulators is an error                 */
		char*  tmp = adb_query("host:devices");
		char*  p   = tmp;
		if(!tmp) {
			printf("no emulator connected\n");
			return -1;
		}
		while (*p) {
			char*  q = strchr(p, '\n');
			if (q != NULL)
				*q++ = 0;
			else
				q = p + strlen(p);

			if (!memcmp(p, LOCAL_CLIENT_PREFIX, sizeof(LOCAL_CLIENT_PREFIX)-1)) {
				if (serial != NULL) {  /* more than one emulator listed */
					free(tmp);
					return -2;
				}
				serial = p;
			}

			p = q;
		}
		free(tmp);

		if (serial == NULL)
			return -1;  /* no emulator found */
	}
	else {
		if (memcmp(serial, LOCAL_CLIENT_PREFIX, sizeof(LOCAL_CLIENT_PREFIX)-1) != 0)
			return -1;  /* not an emulator */
	}

	serial += sizeof(LOCAL_CLIENT_PREFIX)-1;
	port    = strtol(serial, NULL, 10);
	return port;
}

static char __adb_error[256] = { 0 };

const char *adb_error(void)
{
	return __adb_error;
}

static int switch_socket_transport(int fd)
{
	char service[64];
	char tmp[5];
	int len;

	if (__adb_serial)
		//  snprintf(service, sizeof service, "host:transport:%s", __adb_serial);
		sprintf(service,"host:transport:%s", __adb_serial);
	else {
		char* transport_type = "???";

		switch (__adb_transport) {
			case kTransportUsb:
				transport_type = "transport-usb";
				break;
			case kTransportLocal:
				transport_type = "transport-local";
				break;
			case kTransportAny:
				transport_type = "transport-any";
				break;
			case kTransportHost:
				// no switch necessary
				return 0;
				break;
		}

		sprintf(service, "host:%s", transport_type);

	}
	len = strlen(service);
	sprintf(tmp, "%04x", len);

	if(writex(fd, tmp, 4) || writex(fd, service, len)) {
		strcpy(__adb_error, "write failure during connection");
		adb_close(fd);
		return -1;
	}
	D("Switch transport in progress\n");

	if(adb_status(fd)) {
		adb_close(fd);
		D("Switch transport failed\n");
		return -1;
	}
	D("Switch transport success\n");
	return 0;
}

int adb_status(int fd)
{
	unsigned char buf[5];
	unsigned len;

	if(readx(fd, buf, 4)) {
		strcpy(__adb_error, "protocol fault (no status)");
		return -1;
	}

	if(!memcmp(buf, "OKAY", 4)) {
		return 0;
	}

	if(memcmp(buf, "FAIL", 4)) {
		sprintf(__adb_error,
				"protocol fault (status %02x %02x %02x %02x?!)",
				buf[0], buf[1], buf[2], buf[3]);
		return -1;
	}

	if(readx(fd, buf, 4)) {
		strcpy(__adb_error, "protocol fault (status len)");
		return -1;
	}
	buf[4] = 0;
	len = strtoul((char*)buf, 0, 16);
	if(len > 255) len = 255;
	if(readx(fd, __adb_error, len)) {
		strcpy(__adb_error, "protocol fault (status read)");
		return -1;
	}
	__adb_error[len] = 0;
	return -1;
}

int _adb_connect(const char *service)
{
	char tmp[5];
	int len;
	int fd;

	D("_adb_connect: %s\n", service);
	len = strlen(service);
	if((len < 1) || (len > 1024)) {
		strcpy(__adb_error, "service name too long");
		return -1;
	}

	sprintf(tmp, "%04x", len);

	fd = socket_loopback_client(__adb_server_port, SOCK_STREAM);
	if(fd < 0) {
		strcpy(__adb_error, "cannot connect to daemon");
		return -2;
	}

	if (memcmp(service,"host",4) != 0 && switch_socket_transport(fd)) {
		return -1;
	}

	if(writex(fd, tmp, 4) || writex(fd, service, len)) {
		strcpy(__adb_error, "write failure during connection");
		adb_close(fd);
		return -1;
	}

	if(adb_status(fd)) {
		adb_close(fd);
		return -1;
	}

	return fd;
}

int adb_connect(const char *service)
{
	// first query the adb server's version
	int fd = _adb_connect("host:version");

	if(fd == -2) {
		fprintf(stdout,"* daemon not running. starting it now on port %d *\n",
				__adb_server_port);
start_server:
		if(launch_server(__adb_server_port)) {
			fprintf(stderr,"* failed to start daemon *\n");
			return -1;
		} else {
			fprintf(stdout,"* daemon started successfully *\n");
		}
		/* give the server some time to start properly and detect devices */
		adb_sleep_ms(3000);
		// fall through to _adb_connect
	} else {
		// if server was running, check its version to make sure it is not out of date
		char buf[100];
		int n;
		int version = ADB_SERVER_VERSION - 1;

		// if we have a file descriptor, then parse version result
		if(fd >= 0) {
			if(readx(fd, buf, 4)) goto error;

			buf[4] = 0;
			n = strtoul(buf, 0, 16);
			if(n > (int)sizeof(buf)) goto error;
			if(readx(fd, buf, n)) goto error;
			adb_close(fd);

			if (sscanf(buf, "%04x", &version) != 1) goto error;
		} else {
			// if fd is -1, then check for "unknown host service",
			// which would indicate a version of adb that does not support the version command
			if (strcmp(__adb_error, "unknown host service") != 0)
				return fd;
		}

		if(version != ADB_SERVER_VERSION) {
			printf("adb server is out of date.  killing...\n");
			fd = _adb_connect("host:kill");
			adb_close(fd);

			/* XXX can we better detect its death? */
			adb_sleep_ms(2000);
			goto start_server;
		}
	}

	// if the command is start-server, we are done.
	if (!strcmp(service, "host:start-server"))
		return 0;

	fd = _adb_connect(service);
	if(fd == -2) {
		fprintf(stderr,"** daemon still not running");
	}

	return fd;
error:
	adb_close(fd);
	return -1;
}


int adb_command(const char *service)
{
	int fd = adb_connect(service);
	if(fd < 0) {
		return -1;
	}

	if(adb_status(fd)) {
		adb_close(fd);
		return -1;
	}

	return 0;
}

char *adb_query(const char *service)
{
	char buf[5];
	unsigned n;
	char *tmp;

	D("adb_query: %s\n", service);
	int fd = adb_connect(service);
	if(fd < 0) {
		fprintf(stderr,"error: %s\n", __adb_error);
		return 0;
	}

	if(readx(fd, buf, 4)) goto oops;

	buf[4] = 0;
	n = strtoul(buf, 0, 16);
	if(n > 1024) goto oops;

	tmp = (char *)malloc(n + 1);
	if(tmp == 0) goto oops;

	if(readx(fd, tmp, n) == 0) {
		tmp[n] = 0;
		adb_close(fd);
		return tmp;
	}
	free(tmp);

oops:
	adb_close(fd);
	return 0;
}
